package com.googlecode.alfrescocachingserver.services.impl;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.httpclient.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.config.ConfigService;
import org.springframework.extensions.config.RemoteConfigElement;
import org.springframework.extensions.config.RemoteConfigElement.EndpointDescriptor;
import org.springframework.extensions.surf.exception.WebScriptsPlatformException;
import org.springframework.extensions.webscripts.connector.Connector;
import org.springframework.extensions.webscripts.connector.ConnectorContext;
import org.springframework.extensions.webscripts.connector.ConnectorService;
import org.springframework.extensions.webscripts.connector.Response;
import org.springframework.extensions.webscripts.servlet.mvc.ProxyControllerInterceptor;

import com.googlecode.alfrescocachingserver.exceptions.ServiceException;
import com.googlecode.alfrescocachingserver.model.WebScriptResponse;
import com.googlecode.alfrescocachingserver.model.WebScriptUri;
import com.googlecode.alfrescocachingserver.services.WebScriptConnectorService;

public class WebScriptConnectorServiceImpl implements WebScriptConnectorService
{
    private static final Logger LOG = LoggerFactory.getLogger(WebScriptConnectorServiceImpl.class);

    private static final String JSESSIONID = ";jsessionid=";
    
    protected ConfigService configService;
    protected ConnectorService connectorService;
    
    protected ProxyControllerInterceptor proxyControllerInterceptor = new ProxyControllerInterceptor()
    {
        public boolean exceptionOnError(EndpointDescriptor endpoint, String uri)
        {
            return false;
        }
        
        public boolean allowHttpBasicAuthentication(EndpointDescriptor endpoint, String uri)
        {
            return false;
        }
    };
    
    // Service cached values
    protected RemoteConfigElement config;

    /**
     * Sets the config service.
     * 
     * @param configService the new config service
     */
    public void setConfigService(ConfigService configService)
    {
        this.configService = configService;
    }
    
    public void setConnectorService(ConnectorService connectorService)
    {
        this.connectorService = connectorService;
    }

    @Override
    public WebScriptResponse processRequest(WebScriptUri webScriptUri, String contentType, String userId)
    {
//        String uri = url;
//        String queryString = null;
//
//        int index = url.indexOf("?");
//        if (index > -1)
//        {
//            uri = url.substring(index);
//            queryString = url.substring(index, url.length());
//        }
//        else
//        {
//            uri = url;
//        }
//        
//        // handle Flash uploader specific jsession parameter for conforming to servlet spec on later TomCat 6/7 versions
//        int jsessionidIndex = uri.indexOf(JSESSIONID);
//        if ((jsessionidIndex = uri.indexOf(JSESSIONID)) != -1)
//        {
//            uri = uri.substring(0, jsessionidIndex);
//        }
        
//        // validate and return the endpoint id from the URI path
//        StringTokenizer t = new StringTokenizer(URLEncoder.encodeUri(uri), "/");
//        if (!t.hasMoreTokens())
//        {
//            throw new IllegalArgumentException("Proxy URL did not specify endpoint id.");
//        }
//        String endpointId = t.nextToken();
        
        String endpointId = webScriptUri.getEndpoint();
        
//        // rebuild rest of the URL for the proxy request
//        StringBuilder buf = new StringBuilder(64);
//        if (t.hasMoreTokens())
//        {
//            do
//            {
//                buf.append('/');
//                buf.append(t.nextToken());
//            }
//            while (t.hasMoreTokens());
//        }
//        else
//        {
//            // allow for an empty uri to be passed in
//            // this could therefore refer to the root of a service i.e. /webapp/axis
//            buf.append('/');
//        }
        
        try
        {
            // retrieve the endpoint descriptor - do not allow proxy access to unsecure endpoints
            EndpointDescriptor descriptor = getRemoteConfig().getEndpointDescriptor(endpointId);
            if (descriptor == null || descriptor.getUnsecure())
            {
                // throw an exception if endpoint ID is does not exist or invalid
                throw new WebScriptsPlatformException("Invalid EndPoint Id: " + endpointId);
            }
            
            // user id from session NOTE: @see org.springframework.extensions.surf.UserFactory
//            Connector connector;
//            String userId = null;
//            HttpSession session = req.getSession(false);
//            if (session != null)
//            {
//                userId = (String)session.getAttribute(USER_ID);
//            }
//
//            if (userId != null && this.connectorService.getCredentialVault(req.getSession(), userId).hasCredentials(endpointId))
//            {
//                // build an authenticated connector - as we have a userId
//                connector = this.connectorService.getConnector(endpointId, userId, req.getSession());
//            }
//            else if (descriptor.getIdentity() == IdentityType.NONE ||
//                     descriptor.getIdentity() == IdentityType.DECLARED ||
//                     descriptor.getExternalAuth())
//            {
//                // the authentication for this endpoint is either not required, declared in config or
//                // managed "externally" (i.e. by a servlet filter such as NTLM) - this means we should
//                // proceed on the assumption it will be dealt with later
//                connector = this.connectorService.getConnector(endpointId, req.getSession());
//            }
//            else if (descriptor.getBasicAuth() || this.proxyControllerInterceptor.allowHttpBasicAuthentication(descriptor, uri))
//            {
//                // check for HTTP authorisation request (i.e. RSS feeds, direct links etc.)
//                String authorization = req.getHeader("Authorization");
//                if (authorization == null || authorization.length() == 0)
//                {
//                    authorizedResponseStatus(res);
//                    
//                    // no further processing as authentication is required but not provided
//                    // the browser will now prompt the user for appropriate credentials
//                    return null;
//                }
//                else
//                {
//                    // user has provided authentication details with the request
//                    String[] authParts = authorization.split(" ");
//                    // test for a "negotiate" header - we will then suggest "basic" as the auth mechanism
//                    if (authParts[0].equalsIgnoreCase("negotiate"))
//                    {
//                       authorizedResponseStatus(res);
//                       
//                       // no further processing as authentication is required but not provided
//                       // the browser will now prompt the user for appropriate credentials
//                       return null;
//                    }
//                    if (!authParts[0].equalsIgnoreCase("basic"))
//                    {
//                        throw new WebScriptsPlatformException("Authorization '" + authParts[0] + "' not supported.");
//                    }
//                    
//                    String[] values = new String(Base64.decode(authParts[1])).split(":");
//                    if (values.length == 2)
//                    {
//                        if (LOG.isDebugEnabled())
//                            LOG.debug("Authenticating (BASIC HTTP) user " + values[0]);
//                        
//                        // assume username and password passed as the parts and
//                        // build an unauthenticated authentication connector then
//                        // apply the supplied credentials to it
//                        connector = this.connectorService.getConnector(endpointId, values[0], req.getSession());
//                        Credentials credentials = new CredentialsImpl(endpointId);
//                        credentials.setProperty(Credentials.CREDENTIAL_USERNAME, values[0]);
//                        credentials.setProperty(Credentials.CREDENTIAL_PASSWORD, values[1]);
//                        connector.setCredentials(credentials);
//                    }
//                    else
//                    {
//                        authorizedResponseStatus(res);
//                        
//                        // no further processing as authentication is required but not provided
//                        // the browser will now prompt the user for appropriate credentials
//                        return null;
//                    }
//                }
//            }
//            else
//            {
//                res.setStatus(HttpServletResponse.SC_UNAUTHORIZED,
//                        "No USER_ID found in session and requested endpoint requires authentication.");
//                
//                // no further processing as authentication is required but not provided
//                return null;
//            }
            
            Connector connector = connectorService.getConnector(endpointId);
            
            // build a connector context, stores information about how we will drive the remote client
            // ensure we don't proxy over any browser to web-tier Authorization headers over to the endpoint
            Map<String, String> headers = new HashMap<String, String>(1, 1.0f);
            headers.put("Authorization", null);
            ConnectorContext context = new ConnectorContext(webScriptUri.getHttpMethod(), null, headers);
            context.setExceptionOnError(this.proxyControllerInterceptor.exceptionOnError(descriptor, webScriptUri.getUriWithQueryString()));
            context.setContentType(contentType);
            
            // build proxy URL referencing the endpoint
//            final String q = req.getQueryString();
//            final String connectorUrl = buf.toString() + (queryString != null && queryString.length() != 0 ? "?" + queryString : "");
            final String connectorUrl = webScriptUri.getUriWithQueryString();
            
            if (LOG.isDebugEnabled())
            {
                LOG.debug("EndPointProxyController preparing to proxy:");
                LOG.debug(" - endpointId: " + endpointId);
                LOG.debug(" - userId: " + userId);
                LOG.debug(" - connector: " + connector);
                LOG.debug(" - method: " + context.getMethod());
                LOG.debug(" - url: " + connectorUrl);
            }
            
            CaptureHttpServletResponse capturedResponse = new CaptureHttpServletResponse(); 
            
            // call through using our connector to proxy
//            Response response = connector.call(url, context, req, res);
            Response response = connector.call(connectorUrl, context, null, capturedResponse);
            
            if (LOG.isDebugEnabled())
            {
                if (capturedResponse.getHeaderNames() != null)
                {
                    
                    for (String headerName : capturedResponse.getHeaderNames())
                    {
                        LOG.debug("context header:  name = {}, value = {}", headerName, capturedResponse.getHeaders(headerName));
                    }
                }
                
                LOG.debug("Return code: " + response.getStatus().getCode());
                if (response.getStatus().getCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR)
                {
                    String message = "Error detected: " + response.getStatus().getMessage();
                    
                    if (response.getStatus().getException() != null)
                    {
                        message += "\n" + response.getStatus().getException().toString();
                    }
                            
                    LOG.debug(message);
                }
            }
            
            if (response.getStatus().getCode() != HttpStatus.SC_OK)
            {
                throw new ServiceException("Status code = " + response.getStatus().getCode() + ", message = " + response.getStatus().getMessage());
            }
            
            WebScriptResponse webScriptResponse = new WebScriptResponse();
            webScriptResponse.setEncoding(response.getEncoding());
            webScriptResponse.setHeaderMap(capturedResponse.getHeaders());
            webScriptResponse.setResponseBytes(capturedResponse.getContentAsByteArray());
            
            return webScriptResponse;
        }
        catch (Exception e)
        {
            // TODO: trap and handle errors!
            throw new WebScriptsPlatformException("Error during endpoint proxy processing: " + e.getMessage(), e);
        }
    }
    
    /**
     * Gets the remote config.
     * 
     * @return the remote config
     */
    private RemoteConfigElement getRemoteConfig()
    {
        if (this.config == null)
        {
            // retrieve the remote configuration
            this.config = (RemoteConfigElement) configService.getConfig("Remote").getConfigElement("remote");
        }
        
        return this.config;
    }
    
}
